#pragma once

#include "Layer.h"

/// @file

/**
 * \namespace pcpp
 * \brief The main namespace for the PcapPlusPlus lib
 */
namespace pcpp
{

	/**
	 * An enum representing the available ICMPv6 message types
	 */
	enum class ICMPv6MessageType : int
	{
		/** Unknown ICMPv6 message */
		ICMPv6_UNKNOWN_MESSAGE = 0,
		/** Destination Unreachable Message */
		ICMPv6_DESTINATION_UNREACHABLE = 1,
		/** Packet Too Big Message */
		ICMPv6_PACKET_TOO_BIG = 2,
		/** Time Exceeded Message */
		ICMPv6_TIME_EXCEEDED = 3,
		/** Parameter Problem Message */
		ICMPv6_PARAMETER_PROBLEM = 4,
		/** Private Experimentation Message */
		ICMPv6_PRIVATE_EXPERIMENTATION1 = 100,
		/** Private Experimentation Message */
		ICMPv6_PRIVATE_EXPERIMENTATION2 = 101,
		/** Reserved for expansion of ICMPv6 error messages */
		ICMPv6_RESERVED_EXPANSION_ERROR = 127,
		/** Echo Request Message */
		ICMPv6_ECHO_REQUEST = 128,
		/** Echo Reply Message */
		ICMPv6_ECHO_REPLY = 129,
		/** Multicast Listener Query Message */
		ICMPv6_MULTICAST_LISTENER_QUERY = 130,
		/** Multicast Listener Report Message */
		ICMPv6_MULTICAST_LISTENER_REPORT = 131,
		/** Multicast Listener Done Message */
		ICMPv6_MULTICAST_LISTENER_DONE = 132,
		/** Router Solicitation Message */
		ICMPv6_ROUTER_SOLICITATION = 133,
		/** Router Advertisement Message */
		ICMPv6_ROUTER_ADVERTISEMENT = 134,
		/** Neighbor Solicitation Message */
		ICMPv6_NEIGHBOR_SOLICITATION = 135,
		/** Neighbor Advertisement Message */
		ICMPv6_NEIGHBOR_ADVERTISEMENT = 136,
		/** Redirect Message */
		ICMPv6_REDIRECT_MESSAGE = 137,
		/** Router Renumbering Message */
		ICMPv6_ROUTER_RENUMBERING = 138,
		/** Node Information Query Message */
		ICMPv6_ICMP_NODE_INFORMATION_QUERY = 139,
		/** Node Information Reply Message*/
		ICMPv6_ICMP_NODE_INFORMATION_RESPONSE = 140,
		/** Inverse Neighbor Discovery Solicitation Message */
		ICMPv6_INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION_MESSAGE = 141,
		/** Inverse Neighbor Discovery Advertisement Message */
		ICMPv6_INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT_MESSAGE = 142,
		/** Multicast Listener Report Message */
		ICMPv6_MULTICAST_LISTENER_DISCOVERY_REPORTS = 143,
		/** Home Agent Address Discovery Request Message */
		ICMPv6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST_MESSAGE = 144,
		/** Home Agent Address Discovery Reply Message */
		ICMPv6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY_MESSAGE = 145,
		/** Mobile Prefix Solicitation Message */
		ICMPv6_MOBILE_PREFIX_SOLICITATION = 146,
		/** Mobile Prefix Advertisement Message */
		ICMPv6_MOBILE_PREFIX_ADVERTISEMENT = 147,
		/** Certification Path Solicitation Message */
		ICMPv6_CERTIFICATION_PATH_SOLICITATION = 148,
		/** Certification Path Advertisement Message */
		ICMPv6_CERTIFICATION_PATH_ADVERTISEMENT = 149,
		/** ICMP Experimental Mobility Subtype Format and Registry Message */
		ICMPv6_EXPERIMENTAL_MOBILITY = 150,
		/** Multicast Router Advertisement Message */
		ICMPv6_MULTICAST_ROUTER_ADVERTISEMENT = 151,
		/** Multicast Router Solicitation Message */
		ICMPv6_MULTICAST_ROUTER_SOLICITATION = 152,
		/** Multicast Router Termination Message*/
		ICMPv6_MULTICAST_ROUTER_TERMINATION = 153,
		/** RPL Control Message */
		ICMPv6_RPL_CONTROL_MESSAGE = 155,
		/** Private Experimentation Message */
		ICMPv6_PRIVATE_EXPERIMENTATION3 = 200,
		/** Private Experimentation Message */
		ICMPv6_PRIVATE_EXPERIMENTATION4 = 201,
		/** Reserved for expansion of ICMPv6 informational messages */
		ICMPv6_RESERVED_EXPANSION_INFORMATIONAL = 255
	};

/**
 * @struct icmpv6hdr
 * Represents an ICMPv6 protocol header
 */
#pragma pack(push, 1)
	struct icmpv6hdr
	{
		/** Type of the message. Values in the range from 0 to 127 (high-order bit is 0) indicate an error message,
		while values in the range from 128 to 255 (high-order bit is 1) indicate an information message. */
		uint8_t type;
		/** The code field value depends on the message type and provides an additional level of message granularity */
		uint8_t code;
		/** The checksum field provides a minimal level of integrity verification for the ICMP message */
		uint16_t checksum;
	};
#pragma pack(pop)

/**
 * @struct icmpv6_echo_hdr
 * ICMP echo request/reply message structure
 */
#pragma pack(push, 1)
	typedef struct icmpv6_echo_hdr : icmpv6hdr
	{
		/** the echo request identifier */
		uint16_t id;
		/** the echo request sequence number */
		uint16_t sequence;
	} icmpv6_echo_hdr;
#pragma pack(pop)

	/**
	 * @class IcmpV6Layer
	 * Base class for ICMPv6 protocol layers which provides common logic for ICMPv6 messages.
	 */
	class IcmpV6Layer : public Layer
	{
	public:
		/**
		 * A constructor that creates the layer from an existing packet raw data
		 * @param data A pointer to the raw data
		 * @param dataLen Size of the data in bytes
		 * @param prevLayer A pointer to the previous layer
		 * @param packet A pointer to the Packet instance where layer will be stored in
		 */
		IcmpV6Layer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
		    : Layer(data, dataLen, prevLayer, packet)
		{
			m_Protocol = ICMPv6;
		}

		/**
		 * A constructor that allocates a new ICMPv6 layer with type, code and data
		 * @param[in] msgType Message type of the ICMPv6 layer
		 * @param[in] code Code field of the ICMPv6 layer
		 * @param[in] data A pointer to the payload to set
		 * @param[in] dataLen The length of the payload
		 */
		IcmpV6Layer(ICMPv6MessageType msgType, uint8_t code, const uint8_t* data, size_t dataLen);

		virtual ~IcmpV6Layer()
		{}

		/**
		 * A static method that creates an ICMPv6 layer from packet raw data
		 * @param[in] data A pointer to the raw data
		 * @param[in] dataLen Size of the data in bytes
		 * @param[in] prevLayer A pointer to the previous layer
		 * @param[in] packet A pointer to the Packet instance where layer will be stored
		 * @return Layer* A newly allocated ICMPv6 layer
		 */
		static Layer* parseIcmpV6Layer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);

		/**
		 * @param[in] type Type to check
		 * @return True if the layer if of the given type, false otherwise
		 */
		bool isMessageOfType(ICMPv6MessageType type) const
		{
			return getMessageType() == type;
		}

		/**
		 * @return Get the ICMPv6 Message Type
		 */
		ICMPv6MessageType getMessageType() const;

		/**
		 * @return Get the code header field
		 */
		uint8_t getCode() const;

		/**
		 * @return Get the checksum header field in host representation
		 */
		uint16_t getChecksum() const;

		/**
		 * Does nothing for this layer. ICMPv6 is the last layer.
		 */
		void parseNextLayer()
		{}

		/**
		 * @return The size of the ICMPv6 message
		 */
		size_t getHeaderLen() const
		{
			return m_DataLen;
		}

		/**
		 * Calculate ICMPv6 checksum field
		 */
		void computeCalculateFields();

		OsiModelLayer getOsiModelLayer() const
		{
			return OsiModelNetworkLayer;
		}

		std::string toString() const;

	protected:
		IcmpV6Layer() = default;

	private:
		void calculateChecksum();
		icmpv6hdr* getIcmpv6Header() const
		{
			return (icmpv6hdr*)m_Data;
		}
	};

	/**
	 * @class ICMPv6EchoLayer
	 * Represents an ICMPv6 echo request/reply protocol layer
	 */
	class ICMPv6EchoLayer : public IcmpV6Layer
	{
	public:
		/**
		 * An enum representing ICMPv6 echo message types
		 */
		enum ICMPv6EchoType
		{
			/** Echo Request Type */
			REQUEST,
			/** Echo Reply Type */
			REPLY
		};

		/**
		 * A constructor that creates the layer from an existing packet raw data
		 * @param[in] data A pointer to the raw data
		 * @param[in] dataLen Size of the data in bytes
		 * @param[in] prevLayer A pointer to the previous layer
		 * @param[in] packet A pointer to the Packet instance where layer will be stored in
		 */
		ICMPv6EchoLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
		    : IcmpV6Layer(data, dataLen, prevLayer, packet)
		{}

		/**
		 * A constructor for a new echo request/reply layer
		 * @param[in] echoType The type of the echo message
		 * @param[in] id Echo request identifier
		 * @param[in] sequence Echo request sequence number
		 * @param[in] data A pointer to echo request payload to set
		 * @param[in] dataLen The length of the echo request payload
		 */
		ICMPv6EchoLayer(ICMPv6EchoType echoType, uint16_t id, uint16_t sequence, const uint8_t* data, size_t dataLen);

		virtual ~ICMPv6EchoLayer()
		{}

		/**
		 * @return Identifier in host representation
		 */
		uint16_t getIdentifier() const;

		/**
		 * @return Sequence number in host representation
		 */
		uint16_t getSequenceNr() const;

		/**
		 * @return Size of the data in bytes
		 */
		size_t getEchoDataLen() const
		{
			return m_DataLen - sizeof(icmpv6_echo_hdr);
		}

		/**
		 * @return Pointer to the beginning of the data
		 */
		uint8_t* getEchoDataPtr() const
		{
			return m_Data + sizeof(icmpv6_echo_hdr);
		}

		std::string toString() const;

	private:
		icmpv6_echo_hdr* getEchoHeader() const
		{
			return (icmpv6_echo_hdr*)m_Data;
		}
	};

}  // namespace pcpp
